// Usage: ./git-server -repo=/xxx/xxx/xxx/ -port=8884
package main

import (
	"errors"
	"flag"
	"fmt"
	cutils "go-git-protocols/lib"
	"io"
	"log"
	"net"
	"os"
	"os/exec"
	"strings"
)

var repoRoot *string

func main() {
	repoRoot = flag.String("repo", "/Users/zoker/Tmp/repositories/", "Specify a repositories dir.")
	port := flag.String("port", "8884", "Specify a port to start process.")
	flag.Parse()

	addr := fmt.Sprintf(":%s", *port)
	log.Printf("Starting git daemon on port %s \n", *port)
	listener, err := net.Listen("tcp", addr)
	if err != nil {
		log.Println(err.Error())
		os.Exit(1)
	}

	// receive request and process
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Println(err.Error())
			exitSession(conn, err)
		}
		go handleRequest(conn)
	}
}

func handleRequest(conn net.Conn) {
	service, repo, err := parseData(conn)
	if err != nil {
		exitSession(conn, err)
	}
	if service != "git-upload-pack" && service != "git-receive-pack" {
		exitSession(conn, errors.New("Not allowed command. \n"))
	}
	repoPath := fmt.Sprintf("%s%s", *repoRoot, repo)
	if err := cutils.CheckRepo(repoPath, service); err != nil {
		exitSession(conn, err)
	}

	cmdPack := exec.Command("git", service[4:], repoPath)
	cmdStdin, err := cmdPack.StdinPipe()
	cmdStdout, err := cmdPack.StdoutPipe()
	err = cmdPack.Start()

	defer func() {
		_ = conn.Close()
		_ = cmdStdin.Close()
		_ = cmdStdout.Close()
	}()

	if err != nil {
		exitSession(conn, err)
	}

	go func() { _, _ = io.Copy(cmdStdin, conn) }()
	_, _ = io.Copy(conn, cmdStdout)
	err = cmdPack.Wait()

	if err != nil {
		log.Println(err.Error())
	}
}

func exitSession(conn net.Conn, err error) {
	errStr := fmt.Sprintf("ERR %s", err.Error())
	pktData := fmt.Sprintf("%04x%s", len(errStr)+4, errStr)
	_, _ = conn.Write([]byte(pktData))
	_ = conn.Close()
}

func parseData(conn net.Conn) (service, repo string, err error) {
	var b [1024]byte
	var seq int
	n, _ := conn.Read(b[0:])

	// only allowed first pkt-line message length between 21 and 1000
	if n < 21 || n > 1000 {
		exitSession(conn, errors.New("Request message too short(<21) or too large(>1000 include repo name and hostname)\n"))
	}

	// get first \0 position
	for seq = 4; seq < n; seq++ {
		if b[seq] == 0 {
			break
		}
	}

	srArr := strings.Split(string(b[4:seq]), " ")

	if len(srArr) == 2 {
		return srArr[0], srArr[1], err
	} else {
		return "", "", errors.New("Something wrong with request params\n")
	}
}
